和 Python 相关的异步
可以参考的一些资料
Python asyncio 的基本模型
asyncio 模型其实只存在一个线程,本质和 JS 是一样的,使用协作式多任务(cooperative multitasking),所有的任务通过 await 主动让出控制权,在编程的时候,对于一些阻塞主线程的操作就可以使用 await

控制权的转换
Python asyncio 的核心就是控制权的转换,当一个协程 await 某个异步操作时,当前协程暂停执行,控制权交还给“事件循环”,事件循环再调度其他“准备好执行”的协程
| 角色 | 说明 |
|---|---|
| 事件循环(Event Loop) | 控制所有协程的调度,管理任务队列 |
| 协程对象(Coroutine Object) | async def 函数调用的结果,是可以“暂停和恢复”的对象 |
| Task | 协程的包装器,让事件循环能追踪其状态(Pending、Running、Done) |
| Future | 表示一个“将来才有结果”的对象,可能是网络、I/O 等 |
具体的实现:
python
import asyncio
async def download():
print('开始下载!')
await asyncio.sleep(1)
print('下载完成')
return "Ciallo"
async def main():
results = await asyncio.gather(download(), download())
print(results)
asyncio.run(main())asyncio.run(main())启动顶层协程- 在
main()中,使用asyncio.gather(),将download()封装成 Task,使得事件循环能追踪到其状态,由事件循环调度 - 开始执行
download()里面的内容的时候,先执行print('开始下载!') - 执行到
await asyncio.sleep(1)的时候- Sleep (1) 返回一个 Future(表示“1 秒后完成”);
- 当前协程挂起,将自己注册为这个 Future 的“回调”;
- 控制权交还给事件循环(event loop);
- 事件循环去执行别的协程
- 1 秒后,Future 标记为 Done
- 事件循环发现 Future 已经完成了,恢复之前挂起的协程,继续执行下面的
print('下载完成')
[foo() Task] ---> await sleep(1) --挂起--> [Event Loop 控制权]
^ |
|<--- sleep 完成 ---- Future.set_result() <---|Task 的作用和封装
- Task 是对协程的封装
- 它的作用是:让事件循环能够调度这个协程,追踪它的执行状态,处理它的完成/异常等
python
import asyncio
async def say_hello():
await asyncio.sleep(1)
print("Hello")
async def main():
task = asyncio.create_task(say_hello()) # <--- 封装成 Task
await task # 等待 task 执行完成
asyncio.run(main())- 其中的
say_hello()就是一个协程对象(coroutine object) asyncio.create_task()就会把协程对象封装为 Task- 然后,事件循环会自动开始执行这个 Task(即把协程注册进去调度)
await task是等待 Task 的执行过程,并不是封装的过程
async def say_hello()[协程对象] --> asyncio.create_task()[封装为Task] --> 送入到 Event Loop本质
无论 await 的是 sleep、socket I/O,还是磁盘 I/O,本质都是:挂起当前协程,把“完成后要做的事”注册进事件循环中,一旦完成,重新唤醒协程继续执行
这就是异步的核心机制,用 Future + 回调唤醒实现“非阻塞的等待”
或者理解为一个大摩天轮,Event Loop 就是摩天轮的骨架,一直在转,而协程就是摩天轮客舱里面的东西,Task 就是客舱外面贴的状态(标志)
实践
网页的异步抓取
python
import aiohttp
import asyncio
# 单个网页的异步抓取
async def fetch(session, url):
async with session.get(url) as response:
print("status:", response.status)
async def main():
urls = ['https://www.juniortree.com', 'https://note.juniortree.com']
# 复用一个连接池
async with aiohttp.ClientSession() as session:
# 任务的一个列表
tasks = [fetch(session, url) for url in urls]
# *tasks 参数解包,等同于 fetch(session, 'https://www.juniortree.com') 和 fetch(session, 'https://note.juniortree.com')
await asyncio.gather(*tasks)
asyncio.run(main())为什么要使用 async with,它可以帮助我们自动管理资源,也可以用 catch/finally 来处理:
python
# 单个网页的异步抓取
async def fetch(session, url):
response = await session.get(url)
try:
print(response.status)
finally:
response.close()python
# 复用一个连接池
session = aiohttp.ClientSession()
try:
tasks = tasks = [fetch(session, url) for url in urls]
# *tasks 参数解包,等同于 fetch(session, 'https://www.juniortree.com') 和 fetch(session, 'https://note.juniortree.com')
await asyncio.gather(*tasks)
finally:
await session.close()这种方法需要自己记得手动关闭连接,最好还是使用 async with